import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

class Socks5Helper {
  RawSocket clientSocket;

  RawSocket targetSocket;

  bool handShakeCompleted = false;

  bool connected = false;

  Socks5Helper(this.clientSocket) {
    clientSocket.listen((event) async {
      if (event == RawSocketEvent.read) {
        if (!handShakeCompleted) {
          // 没有完成
          handShakeCompleted = handShake();
        } else if (!connected) {
          connected = await connect() ;
        } else {
          // 转发数据
          forward(clientSocket, targetSocket);
        }
      }
    }, onDone: () {
      clientSocket?.close();
    }, onError: (e) {
      print('Socks5Helper onError: $e');
    });
  }

  ///1. socks5 握手
  bool handShake() {
    // 获取所有可读字节
    var bytes = clientSocket.read(clientSocket.available());

    var blob = ByteData.sublistView(bytes);

    var request = HandShakeRequest.from(blob);

    if (request.verify()) {
      var method = 0x00;
      // 响应数据
      var response = HandShakeResponse.from(method);
      clientSocket.write(response.toByte());
      return true;
    }
    return false;
  }

  ///2. 建立连接
  Future<bool> connect() async {
    // 获取所有可读字节
    var bytes = clientSocket.read(clientSocket.available());

    var blob = ByteData.sublistView(bytes);

    var request = Socks5Request.from(blob);

    print('cmd: ${request.cmd}');
    if (request.cmd != 0x01) {
      print('Not support.');
      return false;
    }

    try {
      // 获取目标服务器IP地址
      var host = request.getAddress();
      targetSocket = await RawSocket.connect(host, request.dst.port,
          timeout: Duration(seconds: 15));

      const rep = 0x00;
      var bnd = IPv4(targetSocket.address.rawAddress, targetSocket.port);
      var response = Socks5Response.from(rep, bnd);

      // 应答客户端
      clientSocket.write(response.toByte());

      targetSocket.listen(
          (event) {
            if (event == RawSocketEvent.read) {
              // 转发数据
              forward(targetSocket, clientSocket);
            }
          },
          onError: (e) {},
          onDone: () {
            clientSocket?.close();
            targetSocket?.close();
          });
    } catch (e) {
      var host = (await request.getAddress()).host;
      print('connect server[$host] error: $e');

      var bnd = IPv4(InternetAddress.tryParse('127.0.0.1').rawAddress, 8802);
      clientSocket.write(Socks5Response.from(0x06, bnd).toByte());
      await clientSocket?.close();
      return false;
    }
    return true;
  }

  ///3. 转发请求
  void forward(RawSocket input, RawSocket output) {
    // 从输入源读取
    var bytes = input.read(input.available());
    output.write(bytes);
  }
}

/// 握手请求
class HandShakeRequest {
  int ver;

  int nmethods;

  Uint8List methods;

  HandShakeRequest.from(ByteData blob) {
    // 获取第一个字节， 0 代表偏移量
    ver = blob.getUint8(0);

    // 获取第二个字节, 存放后面数据的长度
    nmethods = blob.getUint8(1);
    if (nmethods > 0) {
      methods = blob.buffer.asUint8List(2, nmethods);
    }
  }

  /// 验证数据
  bool verify() {
    if (ver != 0x05) {
      print('version is not supported');
      return false;
    }
    for (var method in methods) {
      if (method == 0x00) {
        // 支持不需要验证
        return true;
      }
    }

    print('verification method is not supported');
    return false;
  }
}

/// 握手响应
class HandShakeResponse {
  // 固定值
  int ver = 0x05;
  int method;

  HandShakeResponse.from(this.method);

  Uint8List toByte() {
    var data = ByteData(2);
    data.setUint8(0, ver);
    data.setUint8(1, method);
    return data.buffer.asUint8List();
  }
}

/// 协商请求
class Socks5Request {
  int ver;
  int cmd;
  int rsv;
  int atyp;

  Dst dst;

  Socks5Request.from(ByteData blob) {
    ver = blob.getUint8(0);
    cmd = blob.getUint8(1);
    rsv = blob.getUint8(2);
    atyp = blob.getUint8(3);

    switch (atyp) {
      // 0x01 IPv4地址，DST.ADDR部分4字节长度
      case 0x01:
        // IPv4 占4个字节，从第4个字节取4个字节的数据
        var addr = blob.buffer.asUint8List(4, 4);

        /// `byteOffset + 2` must be less than or equal to the length of this object.
        // int getUint16(int byteOffset, [Endian endian = Endian.big]);
        // 自动取2个字节
        var port = blob.getUint16(8);
        dst = IPv4(addr, port);

        break;

      // 0x03 域名，DST.ADDR部分第一个字节为域名长度，DST.ADDR剩余的内容为域名，没有\0结尾。
      case 0x03:
        var len = blob.getUint8(4);
        var hostBytes = blob.buffer.asUint8List(5, len);
        var host = ascii.decode(hostBytes);

        var port = blob.getUint16(len + 5);
        dst = Domain(host, port);
        break;

      // 0x04 IPv6地址，16个字节长度。
      case 0x04:
        break;
    }
  }

  /// 获取客户端地址
  Future<InternetAddress> getAddress() async {
    switch (atyp) {
      // 0x01 IPv4地址，DST.ADDR部分4字节长度
      case 0x01:
        return (dst as IPv4).address;
      // 0x03 域名，DST.ADDR部分第一个字节为域名长度，DST.ADDR剩余的内容为域名，没有\0结尾。
      case 0x03:
        var host = (dst as Domain).host;
        var address = await InternetAddress.lookup(host);
        return address.first;
      case 0x04:
        return null;
    }
  }
}

/// 协商响应
class Socks5Response {
  int ver = 0x05;

  int rep;

  int rsv = 0x00;

  int atyp = 0x01;

  Bnd bnd;

  Socks5Response.from(this.rep, this.bnd);

  Uint8List toByte() {
    // 10个字节的数据
    var data = ByteData(10);

    data.setUint8(0, ver);
    data.setUint8(1, rep);
    data.setUint8(2, rsv);
    data.setUint8(3, atyp);

    var rawAddr = (bnd as IPv4).address.rawAddress;

    for (var i = 0; i < 4; i++) {
      // 从下标 4 开始设置
      data.setUint8(4 + i, rawAddr[i]);
    }

    // 设置端口
    data.setUint8(8, bnd.port);
    return data.buffer.asUint8List();
  }
}

class Dst {
  int port;
}

class Bnd {
  int port;
}

class IPv4 implements Dst, Bnd {
  @override
  int port;
  InternetAddress address;

  IPv4(Uint8List addr, this.port) {
    address =
        InternetAddress.fromRawAddress(addr, type: InternetAddressType.IPv4);
  }
}

class IPv6 implements Dst, Bnd {
  @override
  int port;
}

class Domain implements Dst, Bnd {
  @override
  int port;
  String host;

  Domain(this.host, this.port);
}
